package com.ddxz.tool.core.common;

import com.ddxz.tool.core.convert.StringUtil;
import com.ddxz.tool.core.exception.DdxzBaseException;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static com.ddxz.tool.core.exception.DdxzErrorStatus.INVALID_CHARACTER;

/**
 * @author 朱宗刚
 * @version 1.0.0
 * @description com.casic.common.tools.common.SixtyTwoUtil
 * @date 2020/4/25
 */
public class SixtyTwo implements Serializable, Comparable<SixtyTwo> {
    private static final long serialVersionUID = 202004251716L;
    private static final int CHAR_SIZE = 62;
    private static final int HEADER = 0;
    private static final char[] CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9','A', 'B', 'C', 'D',
            'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
            'w', 'x', 'y', 'z'};
    private static final Map<Character, Integer> CHAR_INDEX= new HashMap<>(CHAR_SIZE);
    private static final String CHAR_STR = new String(CHARS);

    static {
        for(int i = 0; i < CHARS.length; ++i) {
            CHAR_INDEX.put(CHARS[i], i);
        }
    }

    public SixtyTwo() {
        content = String.valueOf(CHARS[HEADER]);
        this.size = content.length();
    }

    private SixtyTwo(String content) {
        this.content = content;
        this.size = content.length();
    }

    private Integer size;

    private String content;

    @Override
    public String toString() {
        String rst = StringUtil.ltrim(content, CHARS[HEADER]);
        return StringUtil.isEmpty(rst) ? String.valueOf(CHARS[HEADER]) : rst;
    }

    public Integer getSize() {
        return size;
    }

    private SixtyTwo add(String strVal) {
        int val = 0, i = this.size - 1, j = strVal.length() - 1;
        StringBuilder builder = new StringBuilder();
        for(; i >= 0 && j >= 0; --i, --j) {
            val = CHAR_INDEX.get(this.content.charAt(i)) + CHAR_INDEX.get(strVal.charAt(j)) + val;
            builder.insert(HEADER, CHARS[val % CHAR_SIZE]);
            val /= CHAR_SIZE;
        }

        for(; i >= 0; --i) {
            val = CHAR_INDEX.get(this.content.charAt(i)) + val;
            builder.insert(HEADER, CHARS[val % CHAR_SIZE]);
            val /= CHAR_SIZE;
        }

        if(val != 0) {
            builder.insert(HEADER, CHARS[val]);
        }

        for(; j >= 0; --j) {
            val = CHAR_INDEX.get(strVal.charAt(j)) + val;
            builder.insert(HEADER, CHARS[val % CHAR_SIZE]);
            val /= CHAR_SIZE;
        }

        this.content = builder.toString();
        size = builder.length();

        return this;
    }

    public SixtyTwo add(SixtyTwo sixtyTwo) {
        return add(sixtyTwo.content);
    }

    public static SixtyTwo parseSixtyTwo(String val) {
        val = StringUtil.ltrim(val, CHARS[HEADER]);
        val = StringUtil.isEmpty(val) ? String.valueOf(CHARS[HEADER]) : val;
        SixtyTwo sixtyTwo = new SixtyTwo();
        StringBuilder builder = new StringBuilder();
        for(int j = 0; j < val.length(); ++j) {
            if (CHAR_STR.indexOf(val.charAt(j)) == -1) {
                throw new DdxzBaseException(INVALID_CHARACTER);
            } else {
                builder.append(val.charAt(j));
            }
        }

        sixtyTwo.content = builder.toString();
        sixtyTwo.size = builder.length();

        return sixtyTwo;
    }

    public SixtyTwo addOne() {
        int val = 1;
        StringBuilder builder = new StringBuilder();
        for(int i = size - 1; i >= 0; --i) {
            val = CHAR_INDEX.get(this.content.charAt(i)) + val;
            builder.insert(HEADER, CHARS[val % CHAR_SIZE]);
            val /= CHAR_SIZE;
        }

        if(val != 0) {
            builder.insert(HEADER, CHARS[val]);
        }

        this.content = builder.toString();
        this.size = builder.length();

        return this;
    }

    /**
     * Compares this object with the specified object for order.  Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.
     *
     * <p>The implementor must ensure <tt>sgn(x.compareTo(y)) ==
     * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>.  (This
     * implies that <tt>x.compareTo(y)</tt> must throw an exception iff
     * <tt>y.compareTo(x)</tt> throws an exception.)
     *
     * <p>The implementor must also ensure that the relation is transitive:
     * <tt>(x.compareTo(y)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies
     * <tt>x.compareTo(z)&gt;0</tt>.
     *
     * <p>Finally, the implementor must ensure that <tt>x.compareTo(y)==0</tt>
     * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
     * all <tt>z</tt>.
     *
     * <p>It is strongly recommended, but <i>not</i> strictly required that
     * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>.  Generally speaking, any
     * class that implements the <tt>Comparable</tt> interface and violates
     * this condition should clearly indicate this fact.  The recommended
     * language is "Note: this class has a natural ordering that is
     * inconsistent with equals."
     *
     * <p>In the foregoing description, the notation
     * <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
     * <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
     * <tt>0</tt>, or <tt>1</tt> according to whether the value of
     * <i>expression</i> is negative, zero or positive.
     *
     * @param o the object to be compared.
     * @return a negative integer, zero, or a positive integer as this object
     * is less than, equal to, or greater than the specified object.
     * @throws NullPointerException if the specified object is null
     */
    @Override
    public int compareTo(SixtyTwo o) {
        if(null == o) {
            throw new NullPointerException();
        }

        int tmp = this.size - o.size;

        if(tmp != 0) {
            return tmp > 0 ? 1 : -1;
        }

        for(int i = 0; i < this.size; ++i) {
            tmp = CHAR_INDEX.get(this.content.charAt(i)) - CHAR_INDEX.get(o.content.charAt(i));
            if(tmp != 0) {
                return tmp > 0 ? 1 : -1;
            }
        }

        return 0;
    }

    public SixtyTwo multiple(SixtyTwo sixtyTwo) {
        int val = 0;
        StringBuilder builder = new StringBuilder();
        String tmp = this.content;
        this.content = String.valueOf(CHARS[HEADER]);
        this.size = 1;
        for(int i = sixtyTwo.size - 1, t = 0; i >= 0; --i, ++t) {
            builder.setLength(HEADER);
            for(int j = tmp.length() - 1; j >= 0; --j) {
                val = CHAR_INDEX.get(tmp.charAt(j)) * CHAR_INDEX.get(sixtyTwo.content.charAt(i)) + val;
                builder.insert(HEADER, CHARS[val % CHAR_SIZE]);
                val /= CHAR_SIZE;
            }

            for(int j = 0; j < t; ++j) {
                builder.insert(HEADER, CHARS[HEADER]);
            }

            this.add(SixtyTwo.parseSixtyTwo(builder.toString()));
        }

        return this;
    }

    public static SixtyTwo transform(String val) {
        int idx = 0;
        for(int i = 0; i < val.length(); ++i) {
            idx += val.charAt(i) * (i + 1);
        }

        StringBuilder builder = new StringBuilder();
        while (idx > 0) {
            builder.insert(HEADER, CHARS[idx % CHAR_SIZE]);
            idx /= CHAR_SIZE;
        }

        int maxLength = 3;
        while (builder.length() < maxLength) {
            builder.append(CHARS[HEADER]);
        }

        return new SixtyTwo(builder.toString());
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        SixtyTwo sixtyTwo = (SixtyTwo) obj;
        return Objects.equals(size, sixtyTwo.size) &&
                Objects.equals(content, sixtyTwo.content);
    }

    @Override
    public int hashCode() {
        return Objects.hash(size, content);
    }

    public static SixtyTwo random() {
        long val = CommonUtil.randLong();
        StringBuilder buffer = new StringBuilder();
        while (val > 0) {
            buffer.append(CHARS[(int)(val % CHAR_SIZE)]);
            val /= CHAR_SIZE;
        }

        SixtyTwo sixtyTwo = new SixtyTwo();
        sixtyTwo.content = buffer.toString();
        sixtyTwo.size = buffer.length();

        return sixtyTwo;
    }
}
